﻿#region Librerias

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using ESF.General;
using System.IO;
using Ionic.Zip;
using System.Xml;
using System.Threading;
using System.Threading.Tasks;
using ESF.SFT.Entities;
using ESF.General.Text;
using ESF.SFT.DataAccess;
using System.Net;
using ESF.General.Entity;
using ESF.SFT.Distributor.GeneralDirectory;
using System.Net.Mail;

#endregion


namespace ESF.SFT.Distributor
{
	public partial class ESF_SFT_Distributor : BaseWindowsService
	{
		#region Variables

		public FileSystemWatcher _MainFSW;
		public FileSystemWatcher _DistributeSettingsFSW;
		public FileSystemWatcher _SignedFileExternalProcessDirectoryExitFSW;

		public string [] _SignedFileExtensions;

		public string _SignedFileExternalProcessDirectoryEntry;
		public string _SignedFileExternalProcessDirectoryExit;

		public string _DistributeSettingsFileName;

		public string _ReceiveOriginalDirectoryName;
		public string _ReceiveCopyOKDirectoryName;
		public string _ReceiveCopyErrorDirectoryName;

		public string _ShortNameNotFoundName;

		public string _OnlyUseSendTestEmail;

		public string _NotSignedFileProcessorDirectory;
		public string _WithoutEnterpriseDirectory;

		public List<FileDestination> _FileDestinations;

		#endregion


		#region Constructor

		public ESF_SFT_Distributor()
		{
			InitializeComponent();

			_MainFSW = new FileSystemWatcher();

			_MainFSW.IncludeSubdirectories = false;

			_MainFSW.Created += new FileSystemEventHandler(_MainFSW_Created);


			_SignedFileExternalProcessDirectoryExitFSW = new FileSystemWatcher ();

			_SignedFileExternalProcessDirectoryExitFSW.Created += new FileSystemEventHandler(_SignedFileExternalProcessDirectoryExitFSW_Created);


			_DistributeSettingsFSW = new FileSystemWatcher();

			_DistributeSettingsFSW.Changed += new FileSystemEventHandler(_DistributeSettingsFSW_Changed);


			_FileDestinations = new List<FileDestination> ();
		}

		#endregion


		#region Start Stop

		public override void DoStart()
		{
			_DistributeSettingsFSW.Path = Path.Combine (Log.ApplicationDirectory);

			_DistributeSettingsFileName = Path.Combine (_DistributeSettingsFSW.Path, "DistributeSettings.xml");

			Log.WriteLineHistory ("Using distribute settings from file: {0}", _DistributeSettingsFileName);

			LoadDistributeSettings ();
		}

		
		public override void DoStop()
		{
			_MainFSW.EnableRaisingEvents = false;
		}

		#endregion


		#region Metodos

		private void LoadDistributeSettings ()
		{
			XmlDocument distributeSettings = new XmlDocument ();

			XmlNode node;

			FileDestination currentFileDestination;

			int pendingFileCount;
			

			try
			{
				FileHelper.WaitFileLocked(_DistributeSettingsFileName);

				distributeSettings.Load(_DistributeSettingsFileName);
			}
			catch (Exception pEx)
			{
				Log.Error (pEx);
				return;
			}


			try
			{
				#region General Settings

				_MainFSW.EnableRaisingEvents = false;

				_SignedFileExternalProcessDirectoryExitFSW.EnableRaisingEvents = false;


				node = distributeSettings.DocumentElement ["GeneralSettings"];

				FileHelper.EnsureFolder(node.Attributes["mainEntryDirectory"].Value); 
				
				_MainFSW.Path = node.Attributes["mainEntryDirectory"].Value;
							

				Log.WriteLineHistory("Monitoring main directory at: {0}", _MainFSW.Path);

				
				_NotSignedFileProcessorDirectory = DataHelper.GetAttribute(node, "notSignedFileProcessorDirectory");

				Log.WriteLineHistory("NotSignedFileProcessorDirectory: {0}", _NotSignedFileProcessorDirectory);


				_WithoutEnterpriseDirectory = DataHelper.GetAttribute(node, "withoutEnterpriseDirectory");

				Log.WriteLineHistory("WithoutEnterpriseDirectory: {0}", _WithoutEnterpriseDirectory);
								

				_MainFSW.EnableRaisingEvents = true;


				_SignedFileExtensions = TextHelper.Split(',', DataHelper.GetAttribute (node, "signedFileExtensions")).ToArray();

				_SignedFileExternalProcessDirectoryEntry = DataHelper.GetAttribute(node, "signedFileExternalProcessDirectoryEntry");

				_SignedFileExternalProcessDirectoryExit = DataHelper.GetAttribute(node, "signedFileExternalProcessDirectoryExit");


				if (!DataHelper.IsEmpty(_SignedFileExtensions))
				{
					Log.WriteLineHistory("Signed file extensions: {0}", TextHelper.Concatenate(", ", _SignedFileExtensions));
				}

				if (!string.IsNullOrEmpty(_SignedFileExternalProcessDirectoryEntry))
				{
					Log.WriteLineHistory("Signed files external process directory entry: {0}", _SignedFileExternalProcessDirectoryEntry);

					FileHelper.EnsureFolder(_SignedFileExternalProcessDirectoryEntry);
				}
				
				if (!string.IsNullOrEmpty(_SignedFileExternalProcessDirectoryExit))
				{
					Log.WriteLineHistory("Signed files external process directory exit: {0}", _SignedFileExternalProcessDirectoryExit);
				
					FileHelper.EnsureFolder (_SignedFileExternalProcessDirectoryExit);
					
					_SignedFileExternalProcessDirectoryExitFSW.Path = _SignedFileExternalProcessDirectoryExit;

					_SignedFileExternalProcessDirectoryExitFSW.EnableRaisingEvents = true;
				}


				_ReceiveOriginalDirectoryName = DataHelper.GetAttribute(node, "receiveOriginalDirectoryName", "OriginalEntry");

				_ReceiveCopyOKDirectoryName = DataHelper.GetAttribute(node, "receiveCopyOKDirectoryName", "OK");

				_ReceiveCopyErrorDirectoryName = DataHelper.GetAttribute(node, "receiveCopyErrorDirectoryName", "Error");

				_ShortNameNotFoundName = DataHelper.GetAttribute (node, "shortNameNotFoundName", "NOT_FOUND");

				Log.WriteLineHistory ("Folders of specific distribution used: {0} with {1} and {2} (not found: {3})", 
					_ReceiveOriginalDirectoryName, _ReceiveCopyOKDirectoryName, _ReceiveCopyErrorDirectoryName, _ShortNameNotFoundName);


				_OnlyUseSendTestEmail = DataHelper.GetAttribute (node, "onlyUseSendTestEmail");

				if (!string.IsNullOrWhiteSpace (_OnlyUseSendTestEmail))
				{
					Log.WriteLineHistory ("Using only send test email to: {0}", _OnlyUseSendTestEmail);
				}

				#endregion

				#region FileDistributions

				Log.WriteLineHistory ("Loading File distributions");

				node = distributeSettings.DocumentElement ["FileDestinations"];

				_FileDestinations.Clear ();

				foreach (XmlNode fileDestination in node.ChildNodes)
				{
					currentFileDestination = new FileDestination ()
					{
						StartIdentificator = fileDestination.Attributes["startIdentificator"].Value,
						DirectoryDestination = fileDestination.Attributes["directoryDestination"].Value,					
						ApplicationDestination = DataHelper.GetAttribute (fileDestination, "applicationDestination")
					};

					if (_FileDestinations.Exists (fd => fd.StartIdentificator == currentFileDestination.StartIdentificator))
					{
						throw new Exception (string.Format ("El identificador {0} ya esta asignado", currentFileDestination.StartIdentificator));
					}

					_FileDestinations.Add (currentFileDestination);

					Log.WriteLineHistory(" - Found, StartIdentificator: {0}, ApplyDirectoryDestination {1}, ApplyApplicationDestionation: {2}", 
						currentFileDestination.StartIdentificator, 
						currentFileDestination.ApplyDirectoryDestination,
						currentFileDestination.ApplyApplicationDestination);
				}

				if (!_FileDestinations.Exists (fd => fd.StartIdentificator == _ShortNameNotFoundName))
				{
					throw new Exception(string.Format("El identificador {0} no esta definido, se requiere para darle un destino a los archivos no identificados", _ShortNameNotFoundName));
				}

				foreach (FileDestination fdx in _FileDestinations)
				{
					if (_FileDestinations.Count (fd => fd.StartIdentificator.StartsWith (fdx.StartIdentificator)) > 1)
					{
						throw new Exception (string.Format ("El identificador {1} fue encontrado como parte de otros identificadores lo cual es ambiguo al momento de evaluarlo, cambielo por uno único", fdx.StartIdentificator));
					}
				}

				#endregion

				#region Procesar pendientes

				pendingFileCount = FileHelper.ExecuteInDirectoryFiles(_MainFSW.Path,
					delegate(string pFileName, object[] pParams)
					{
						InitialDistribution(pFileName, true);
					}, pSearchOption: SearchOption.TopDirectoryOnly);

				if (pendingFileCount > 0)
				{
					Log.WriteLineHistory("Found {0} files in main directory, there were processed", pendingFileCount);
				}

				#endregion
			}
			catch (Exception pEx)
			{
				Log.Error (pEx);

				Stop ();
			}
		}


		private void InitialDistribution (string pFileName, bool pForceOverrite = false)
		{
			string tmpFileName;
			string newFileName;
			string guid;

			FileRegisterValidatorSet frvsSelected;

			List<FileRegisterValidatorSet> lstFrvs;

			FileData newFile;

			FileDestination fileDestination;


			try
			{
				guid = AdoFileReceiveLog.GetGUIDVersion(pFileName);

				#region Si no esta lo registra

				if (string.IsNullOrEmpty(guid))
				{
					guid = Guid.NewGuid().ToString();

					using (FileMetadataPublisher.MetadataPublisherClient mpc = new FileMetadataPublisher.MetadataPublisherClient ())
					{
						frvsSelected = mpc.GetValidatorSet("MED114MCOM");

						lstFrvs = mpc.GetAll (string.Empty);

						frvsSelected = lstFrvs.FirstOrDefault (
							fvs => Path.GetFileName(pFileName).StartsWith(fvs.BaseFileName, StringComparison.InvariantCultureIgnoreCase));

						newFile = new FileData(pFileName, frvsSelected);

						AdoFileReceiveLog.Register(new FileReceiveLog()
						{
							FileName = newFile.Name,
							LengthBytes = newFile.Length,
							UploadDateTime = DateTime.Now,
							UploadUserAlias = "_local_",
							UserHostAddress = "127.0.0.1",
							ServerHostName = "localhost",
							ServerHostAddress = "127.0.0.1",
							IdentificationTypeCode = newFile.IdentificationTypeCode,
							IdentificationNumber = newFile.IdentificationNumber,
							ShortIdentificator = (newFile.ValidatorSet == null ? "ERROR" : newFile.ValidatorSet.Name),
							MD5Hash = newFile.MD5Hash,
							GUID = guid
						});
					}
				}

				#endregion

				AdoFileReceiveLog.RegisterArrivalForDistribution(guid);

				// Esta comprimido
				if (Path.GetExtension(pFileName).ToUpper() == ".ZIP")
				{
					using (ZipFile z = new ZipFile(pFileName))
					{
						// TODO: Soportar multiples archivos en un comprimido
						if (z.Entries.Count == 1)
						{
							tmpFileName = FileHelper.GenerateTempFileName();

							using (FileStream tmpFS = new FileStream(tmpFileName, FileMode.Create, FileAccess.ReadWrite))
							{
								z.Entries.First ().Extract(tmpFS);
							}

							newFileName = Path.Combine(_MainFSW.Path, Path.GetFileName(z.Entries.First().FileName));

							AdoFileReceiveLog.RegisterDecompression (guid);

							if (File.Exists(newFileName))
							{
								// Despeja una version que no haya sido procesada
								InitialDistribution(newFileName);
							}

							FileHelper.Move(tmpFileName, newFileName);
						}
						else
						{
							throw new NotSupportedException (string.Format ("El archivo {0} tiene {1} archivos contenidos, esta versión solo soporta uno por archivo", pFileName, z.Entries.Count));
						}
					}

					FileHelper.Delete(pFileName);
				}
				// Esta firmado
				else if (TextHelper.IsIn(Path.GetExtension(pFileName).ToUpper(), _SignedFileExtensions))
				{
					if (!string.IsNullOrEmpty(_SignedFileExternalProcessDirectoryEntry))
					{
						newFileName = Path.Combine(_SignedFileExternalProcessDirectoryEntry, Path.GetFileName(pFileName));

						FileHelper.Move(pFileName, newFileName);

						AdoFileReceiveLog.RegisterDigitalSignedFileMoved (guid);
					}
					else
					{
						newFileName = Path.Combine (
							_NotSignedFileProcessorDirectory,
							DateTime.Now.Year.ToString(),
							DateTime.Now.Month.ToString().PadLeft(2, '0'),
							DateTime.Now.Day.ToString().PadLeft(2, '0'), 
							Path.GetFileName(pFileName));

						FileHelper.Move (pFileName, newFileName);
					}
				}
				// Esta listo para procesamiento (este debe mover los archivos a las ubicaciones de procesamiento)
				else
				{
					fileDestination = 
						(from FD in _FileDestinations
						where 
							Path.GetFileName (pFileName).StartsWith (FD.StartIdentificator, StringComparison.InvariantCultureIgnoreCase)
						select 
							FD).FirstOrDefault ();

					if (fileDestination == null)
					{
						fileDestination = _FileDestinations.First (fd => fd.StartIdentificator == _ShortNameNotFoundName);
					}
						
					newFileName = Path.Combine (
						fileDestination.ApplyDirectoryDestination, 
						_ReceiveOriginalDirectoryName, 
						DateTime.Now.Year.ToString (), 
						DateTime.Now.Month.ToString ().PadLeft (2, '0'),
						DateTime.Now.Day.ToString().PadLeft(2, '0'),
						Path.GetFileName (pFileName));

					FileHelper.Move (pFileName, newFileName);

					if (fileDestination.StartIdentificator != _ShortNameNotFoundName)
					{
						ValidateFile (newFileName, fileDestination, guid);
					}
				}
			}
			catch (Exception pEx)
			{
				pEx.Data.Add("File", pFileName);

				Log.Error(pEx);
			}
		}


		private void ValidateFile(string pFileName, FileDestination pFileDestination, string pGUID)
		{
			FileData fileData;

			FileRegisterValidatorSet applyValSet;

			IEnterprise fileEnterprise;

			string newFileName;

			StringBuilder message;

			MailMessage notificationEmail = null;

			bool wasEmailSend = false;


			try
			{
				AdoFileReceiveLog.RegisterArrivalForValidation (pGUID);

				using (FileMetadataPublisher.MetadataPublisherClient mpc = new FileMetadataPublisher.MetadataPublisherClient ())
				{
					applyValSet = mpc.GetValidatorSet(pFileDestination.StartIdentificator);

					fileData = new FileData (pFileName, applyValSet);

					fileData.Validate ();

					AdoFileReceiveLog.RegisterValidationResult(pGUID, fileData.ErrorsXML);

					newFileName = Path.Combine (
						pFileDestination.ApplyDirectoryDestination,
						fileData.IsValid ? _ReceiveCopyOKDirectoryName : _ReceiveCopyErrorDirectoryName,
						DateTime.Now.Year.ToString(),
						DateTime.Now.Month.ToString().PadLeft(2, '0'),
						DateTime.Now.Day.ToString().PadLeft(2, '0'),
						Path.GetFileName (pFileName));

					FileHelper.Copy (pFileName, newFileName);
					AdoFileReceiveLog.RegisterCopyToReceived(pGUID);

					if (fileData.IsValid)
					{
						using (GeneralDirectoryClient gdc = new GeneralDirectoryClient ())
						{
							fileEnterprise = gdc.GetEnterprise(fileData.IdentificationTypeCode, fileData.IdentificationNumber);
						}

						
						// si no tiene entidad
						if (fileEnterprise == null)
						{
							newFileName = Path.Combine (
								_WithoutEnterpriseDirectory,
								DateTime.Now.Year.ToString(),
								DateTime.Now.Month.ToString().PadLeft(2, '0'),
								DateTime.Now.Day.ToString().PadLeft(2, '0'), 
								Path.GetFileName(pFileName));

							FileHelper.Move (pFileName, newFileName);
						}
						else
						{
							if (!string.IsNullOrEmpty (pFileDestination.ApplicationDestination))
							{
								newFileName = Path.Combine (pFileDestination.ApplicationDestination, Path.GetFileName (pFileName));

								FileHelper.Copy(pFileName, newFileName);
								AdoFileReceiveLog.RegisterCopyToApplication(pGUID);
							}

							message = new StringBuilder ();

							message.AppendFormat ("Sres. <b>{0} ({1} - {2})</b>", fileEnterprise.Name, fileEnterprise.IdentificationType.Code, fileEnterprise.IdentificationNumber);
							message.AppendFormat ("<br /><br />Los siguientes son los resultados de la validación de estructura del archivo {0}, realizada por {1}:<br /><br />",
								fileData.Name, ReadConfig.ApplicationName);

							if (fileData.IsValid)
							{
								message.AppendFormat ("<b>La estructura del archivo es Correcta</b><br /><br />Pasa al Aplicativo Misional para completar su procesamiento, se le notificará por este medio del resultado.");
							}
							else
							{
								message.AppendFormat("<table><tr><th>Línea</th><th>Tipo Reg.</th><th>Campo</th><th>Nombre del Campo</th><th>Valor Errado [valor]</th><th>Detalle del Error</th></tr>");

								foreach (GenericValidationException gve in fileData.Errors)
								{
									message.AppendFormat ("<tr><td>{0}</td><td>{1}</td><td>{2}</td><td>{3}</td><td>{4}</td><td>{5}</td></tr>",
										gve.LineNumber, gve.RelatedRegType, gve.LinePosition, gve.RelatedFieldname, gve.RelatedValue, gve.DetailMessage);
								}

								message.Append ("</table>");
							}

							message.AppendFormat ("<br /><br />Atentamente,<br /><br />{0}", ReadConfig.ApplicationName);


							notificationEmail = new MailMessage ();

							notificationEmail.Subject = string.Format ("{0} – Resultado de la validación de estructura del archivo {1}", 
								ReadConfig.ApplicationName, Path.GetFileName (pFileName));

							if (!string.IsNullOrWhiteSpace (_OnlyUseSendTestEmail))
							{
								notificationEmail.To.Add (_OnlyUseSendTestEmail);
							}
							else if (TextHelper.IsEmail (fileEnterprise.Email))
							{
								notificationEmail.To.Add (fileEnterprise.Email);
							}

							notificationEmail.Body = message.ToString ();

							notificationEmail.IsBodyHtml = true;


							wasEmailSend = EmailSender.Send (notificationEmail, true, false);
						}

						AdoFileReceiveLog.RegisterStatus(pGUID, fileData.IsValid, fileEnterprise != null, notificationEmail, wasEmailSend);
					}
				}
			}
			catch (Exception pEx)
			{
				Log.Error (pEx);
			}
		}

		#endregion


		#region DistributeSettingsFSW Events

		private void _DistributeSettingsFSW_Changed(object sender, FileSystemEventArgs e)
		{
			if (e.Name == "DistributeSettings.xml")
			{
				LoadDistributeSettings ();
			}
		}

		#endregion


		#region MainFSW Events

		private void _MainFSW_Created(object sender, FileSystemEventArgs e)
		{
			string fileName;


			if (!File.Exists (e.FullPath))
			{
				return;
			}

			fileName = e.FullPath;

			Task.Factory.StartNew(() =>
			{
				InitialDistribution (fileName);
			});
		}

		#endregion


		#region SignedFileExternalProcessDirectoryExitFSW Events

		private void _SignedFileExternalProcessDirectoryExitFSW_Created(object sender, FileSystemEventArgs e)
		{
			if (!File.Exists(e.FullPath))
			{
				return;
			}

			Task.Factory.StartNew (() =>
			{
				try
				{
					FileHelper.Move (e.FullPath, Path.Combine (_MainFSW.Path, Path.GetFileName (e.Name)));
				}
				catch (Exception pEx)
				{
					Log.Error (pEx);
				}
			});
		}

		#endregion
	}
}
